    <!DOCTYPE html>
<html lang="zh-cn">
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<meta name="author" content="nodejh">
		<meta name="description" content="My Blog">
		<meta name="generator" content="Hugo 0.31.1" />
		<title>Electron 快速入门 &middot; nodejh</title>
		<link rel="shortcut icon" href="http://nodejh.com/images/favicon.ico">
		<link rel="stylesheet" href="http://nodejh.com/css/style.css">
		<link rel="stylesheet" href="http://nodejh.com/css/highlight.css">
		

		
		<link rel="stylesheet" href="http://nodejh.com/css/font-awesome.min.css">
		

		
		<link href="http://nodejh.com/index.xml" rel="alternate" type="application/rss+xml" title="nodejh" />
		
	</head>

    <body>
       <nav class="main-nav">
	
	
		<a href='http://nodejh.com/'> <span class="arrow">←</span>Home</a>
	
	<a href='http://nodejh.com/post'>Archive</a>
	<a href='http://nodejh.com/tags'>Tags</a>
	<a href='http://nodejh.com/projects'>Projects</a>
	<a href='http://nodejh.com/about'>About</a>

	

	
	<a class="cta" href="http://nodejh.com/index.xml">Subscribe</a>
	
</nav>


        <section id="wrapper" class="post">
            <article>
                <header>
                    <h1>
                        Electron 快速入门
                    </h1>
                    <h2 class="headline">
                    Jul 6, 2017 15:32
                    · 2686 words
                    · 6 minutes read
                      <span class="tags">
                      
                      
                          
                              <a href="http://nodejh.com/tags/electron">electron</a>
                          
                      
                      
                      </span>
                    </h2>
                </header>
                
                  
                    <div id="toc">
                      <nav id="TableOfContents">
<ul>
<li>
<ul>
<li><a href="#简介">简介</a></li>
<li><a href="#安装">安装</a></li>
<li><a href="#进程">进程</a>
<ul>
<li><a href="#主进程">主进程</a></li>
<li><a href="#渲染进程">渲染进程</a></li>
<li><a href="#主进程与渲染进程的联系及区别">主进程与渲染进程的联系及区别</a></li>
</ul></li>
<li><a href="#打造第一个-electron-应用">打造第一个 Electron 应用</a>
<ul>
<li><a href="#main-js">main.js</a></li>
<li><a href="#index-html">index.html</a></li>
</ul></li>
<li><a href="#运行">运行</a></li>
<li><a href="#进行通信">进行通信</a>
<ul>
<li><a href="#ipcmain-和-ipcrenderer">ipcMain 和 ipcRenderer</a></li>
<li><a href="#remote">remote</a></li>
</ul></li>
<li><a href="#打包">打包</a></li>
<li><a href="#总结">总结</a></li>
</ul></li>
</ul>
</nav>
                    </div>
                  
                
                <section id="post-body">
                    

<h2 id="简介">简介</h2>

<p>Electron 是一个可以使用 Web 技术如 JavaScript、HTML 和 CSS 来创建跨平台原生桌面应用的框架。借助 Electron，我们可以使用纯 JavaScript 来调用丰富的原生 APIs。</p>

<!-- more -->

<p>Electron用 web 页面作为它的 GUI，而不是绑定了 GUI 库的 JavaScript。它结合了 Chromium、Node.js 和用于调用操作系统本地功能的 APIs（如打开文件窗口、通知、图标等）。</p>

<p><img src="http://oh1ywjyqf.bkt.clouddn.com/Electron-Quick-Start-00.jpeg" alt="Electron-Quick-Start-00" /></p>

<p>现在已经有很多由 Electron 开发应用，比如 <a href="https://atom.io/">Atom</a>、<a href="https://insomnia.rest/">Insomnia</a>、<a href="https://code.visualstudio.com/">Visual Studio Code</a> 等。查看更多使用 Electron 构建的项目可以访问 <a href="https://electron.atom.io/apps/">Apps Built on Electron
</a></p>

<h2 id="安装">安装</h2>

<p>安装 electron 之前，需要安装 Node.js。如果没有安装，推荐使用 <a href="https://github.com/creationix/nvm/blob/master/README.md">nvm</a> 等 Node.js 版本管理工具进行安装。</p>

<p>然后建议修改 electron 的源为国内源：</p>

<pre><code class="language-bash">$ export ELECTRON_MIRROR=&quot;https://npm.taobao.org/mirrors/electron/&quot;
</code></pre>

<p>不然会出现如下错误：</p>

<pre><code class="language-bash">Error: connect ETIMEDOUT 54.231.50.42:443
    at Object.exports._errnoException (util.js:1016:11)
    at exports._exceptionWithHostPort (util.js:1039:20)
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1138:14)
</code></pre>

<p>安装 electron：</p>

<pre><code class="language-bash">$ npm install electron -g
</code></pre>

<h2 id="进程">进程</h2>

<p>Electron 的进程分为主进程和渲染进程。</p>

<h3 id="主进程">主进程</h3>

<p>在 electron 里面，运行 <code>package.json</code> 里面 <code>main</code> 脚本的进程成为主进程。主进程控制整个应用的生命周期，在主进程中可以创建 Web 形式的 GUI，而且整个 Node API 是内置其中。</p>

<h3 id="渲染进程">渲染进程</h3>

<p>每个 electron 的页面都运行着自己的进程，称为渲染进程。</p>

<h3 id="主进程与渲染进程的联系及区别">主进程与渲染进程的联系及区别</h3>

<p>主进程使用 <code>BrowserWindow</code> 实例创建页面。每个 <code>BrowserWindow</code> 实例都在自己的渲染进程里运行页面。当一个 <code>BrowserWindow</code> 实例被销毁后，相应的渲染进程也会被终止。</p>

<p>主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的，并且只关心他们自己的页面。</p>

<p>在 electron 中，页面不直接调用底层 APIs，而是通过主进程进行调用。所以如果你想在网页里使用 GUI 操作，其对应的渲染进程必须与主进程进行通讯，请求主进程进行相关的 GUI 操作。</p>

<p>在 electron 中，主进程和渲染进程的通信主要有以下几种方式：</p>

<ul>
<li>ipcMain、ipcRender</li>
<li>Remote 模块</li>
</ul>

<p>进程通信将稍后详细介绍。</p>

<h2 id="打造第一个-electron-应用">打造第一个 Electron 应用</h2>

<blockquote>
<p>以下所有代码可以在 <a href="https://github.com/nodejh/electron-quick-start">https://github.com/nodejh/electron-quick-start</a> 找到。</p>
</blockquote>

<p>一个最简单的 electron 应用目录结构如下：</p>

<pre><code class="language-bash">electron-demo/
├── package.json
├── main.js
└── index.html
</code></pre>

<p><code>package.json</code> 与 Node.js 的完全一致，所以我们可以使用 <code>npm init</code> 来生成。然后将 <code>&quot;main&quot;: &quot;index.js&quot;</code> 修改为 <code>&quot;main&quot;: &quot;main.js&quot;</code>。之所以命名为 <code>main.js</code>，主要是为了与主进程这个概念对应。</p>

<h3 id="main-js">main.js</h3>

<p>创建 <code>main.js</code> 文件并添加如下代码：</p>

<pre><code class="language-js">const electron = require('electron');

const {
  app, // 控制应用生命周期的模块
  BrowserWindow, // 创建原生浏览器窗口的模块
} = electron;

// 保持一个对于 window 对象的全局引用，如果不这样做，
// 当 JavaScript 对象被垃圾回收， window 会被自动地关闭
let mainWindow;

function createWindow() {
  // 创建浏览器窗口。
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // 加载应用的 index.html。
  // 这里使用的是 file 协议，加载当前目录下的 index.html 文件。
  // 也可以使用 http 协议，如 mainWindow.loadURL('http://nodejh.com')。
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 启用开发工具。
  mainWindow.webContents.openDevTools();

  // 当 window 被关闭，这个事件会被触发。
  mainWindow.on('closed', () =&gt; {
    // 取消引用 window 对象，如果你的应用支持多窗口的话，
    // 通常会把多个 window 对象存放在一个数组里面，
    // 与此同时，你应该删除相应的元素。
    mainWindow = null;
  });
}

// Electron 会在初始化后并准备
// 创建浏览器窗口时，调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow);

// 当全部窗口关闭时退出。
app.on('window-all-closed', () =&gt; {
  // 在 macOS 上，除非用户用 Cmd + Q 确定地退出，
  // 否则绝大部分应用及其菜单栏会保持激活。
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () =&gt; {
  // 在 macOS 上，当点击 dock 图标并且该应用没有打开的窗口时，
  // 绝大部分应用会重新创建一个窗口。
  if (mainWindow === null) {
    createWindow();
  }
});
</code></pre>

<p>关于 <code>app</code> 和 <code>BrowserWindow</code> 对象和实例的更多用法可参考 electron 的文档：</p>

<ul>
<li><a href="https://github.com/electron/electron/blob/master/docs/api/app.md">app</a></li>
<li><a href="https://github.com/electron/electron/blob/master/docs/api/browser-window.md">BrowserWindow</a></li>
</ul>

<h3 id="index-html">index.html</h3>

<p>然后编辑需要展示的 <code>index.html</code>：</p>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;title&gt;Hello World!&lt;/title&gt;
    &lt;style media=&quot;screen&quot;&gt;
      .version {
        color: red;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Hello World!&lt;/h1&gt;
    We are using Node.js
    &lt;span id=&quot;version-node&quot; class=&quot;version&quot;&gt;&lt;/span&gt;
    and Electron
    &lt;span id=&quot;version-electron&quot; class=&quot;version&quot;&gt;&lt;/span&gt;
    &lt;script type=&quot;text/javascript&quot;&gt;
      console.log('process: ', process);
      var versionNode = process.version;
      var versionElectron = process.versions['electron'];
      document.getElementById('version-node').innerText = versionNode
      document.getElementById('version-electron').innerText = versionElectron
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>在这个例子中，我们显示出了 electron 使用的 Node.js 版本和 electron 的版本。<code>index.html</code> 跟网页的 HTML 一摸一样，只是多了一些 electron 的全局对象。</p>

<h2 id="运行">运行</h2>

<p>因为前面已经全局安装了 electron，所以我们可以使用 electron 命令来运行项目。在 <code>electron-demo/</code> 目录里面运行下面的命令：</p>

<pre><code class="language-bash">$ electron .
</code></pre>

<p>然后会弹出一个 electron 应用客户端，如图所示：</p>

<p><img src="http://oh1ywjyqf.bkt.clouddn.com/Electron-Quick-Start-01.png" alt="Electron-Quick-Start-01" /></p>

<p>因为在主进程中启用了开发模式 <code>mainWindow.webContents.openDevTools()</code>，所以默认启动开发者工具。</p>

<p>如果是局部安装的 electron，即 <code>npm install --save electron</code>，则可以运行下面的命令来启动应用：</p>

<pre><code class="language-bash">$ ./node_modules/.bin/electron .
</code></pre>

<h2 id="进行通信">进行通信</h2>

<p>对于 electron 来说，主进程和渲染进程直接的通信是必不可少的。</p>

<p>前面提到过 electron 进程间的通信的方式主要有两种，一种是用于发送消息的 <a href="https://github.com/electron/electron/blob/master/docs/api/ipc-main.md">ipcMain</a> 和 <a href="https://github.com/electron/electron/blob/master/docs/api/ipc-renderer.md">ipcRenderer</a> 模块，一种用于 RPC 的 <a href="https://github.com/electron/electron/blob/master/docs/api/remote.md">remote</a> 模块。</p>

<p>现在假设一个业务场景，用户在页面中输入文本消息，渲染进程将消息发送给主进程，主进程处理后将处理结果返回给页面。为了方便起见，主进程的处理就假设为翻转文本。当然，这个功能在前端完全可以实现，这里只是为了演示进程通信。</p>

<h3 id="ipcmain-和-ipcrenderer">ipcMain 和 ipcRenderer</h3>

<p>首先在渲染进程中添加一个输入框和一个按钮，并实现点击按钮获取输入框的内容。然后使用 ipcRenderer 发送消息。主进程接收到消息并处理之后，会返回处理结果。所以渲染进程中还需要接收主进程的消息。</p>

<p>修改 <code>index.html</code>，添加下面的代码：</p>

<pre><code class="language-html">&lt;!-- 在 body 部分添加一个输入框和按钮 --&gt;
&lt;div&gt;
  &lt;input type=&quot;text&quot; id=&quot;message&quot; name=&quot;&quot; value=&quot;&quot;&gt;
  &lt;br/&gt;
  &lt;button type=&quot;button&quot; id=&quot;button&quot; name=&quot;button&quot;&gt;click me&lt;/button&gt;
&lt;/div&gt;

&lt;script type=&quot;text/javascript&quot;&gt;
  // ...

  // 添加下面的代码。
  // 引入 ipcRenderer 模块。
  var ipcRenderer = require('electron').ipcRenderer;
  document.getElementById('button').onclick = function () {
    var message = document.getElementById('message').value;
    // 使用 ipcRenderer.send 向主进程发送消息。
    ipcRenderer.send('asynchronous-message', message);
  }

  // 监听主进程返回的消息
  ipcRenderer.on('asynchronous-reply', function (event, arg) {
    alert(arg);
  });
&lt;/script&gt;
</code></pre>

<p>接下来在主进程中接收渲染进程的消息，并进行处理（翻转字符串），然后将处理结果发送给主进程。修改 <code>main.js</code> 如下：</p>

<pre><code class="language-js">//...

// 监听渲染进程发送的消息
ipcMain.on('asynchronous-message', (event, arg) =&gt; {
  const reply = arg.split('').reverse().join('');
  console.log('reply: ', reply);
  // 发送消息到主进程
  event.sender.send('asynchronous-reply', reply);
});
</code></pre>

<p>然后重新运行项目。在页面的输入框内输入字符，点击按钮，就能弹出如下的弹出框，说明渲染进程与主进程通信成功：</p>

<p><img src="http://oh1ywjyqf.bkt.clouddn.com/Electron-Quick-Start-02.png" alt="http://oh1ywjyqf.bkt.clouddn.com/Electron-Quick-Start-02.png" /></p>

<h3 id="remote">remote</h3>

<p><a href="https://github.com/electron/electron/blob/master/docs/api/remote.md">remote</a> 模块提供了一种在渲染进程（网页）和主进程之间进行进程间通讯（IPC）的简便途径。</p>

<p>使用 remote 模块，我们可以很方便地调用主进程对象的方法，而不需要发送消息。</p>

<p>在 <code>index.html</code> 的 <code>&lt;script&gt;</code> 标签中添加如下代码：</p>

<pre><code class="language-js">// 引入 remote 模块
var remote = require('electron').remote;
// 获取主进程中的 BrowserWindow 对象
var BrowserWindow = remote.BrowserWindow;
// 创建一个渲染进程
var win = new BrowserWindow({ width: 200, height: 150 });
win.loadURL('http://nodejh.com');
</code></pre>

<p>然后使用 <code>ctr + r</code> 组合键刷新应用，就会看到创建出的一个新窗口。</p>

<h2 id="打包">打包</h2>

<p>Electron 应用开发完成之后，还需要将其打包成对应平台的客户端。常用的打包工具有 <a href="https://github.com/electron-userland/electron-packager">electron-packager</a> 和 <a href="https://github.com/electron/asar">asar</a>。</p>

<p>这里以 electron-packager 为例。首先全局安装 electron-packager：</p>

<pre><code class="language-bash">$ npm install electron-packager -g
</code></pre>

<p>然后在项目中安装 electron：</p>

<pre><code class="language-bash">$ npm install electron --save-dev
</code></pre>

<p>然后打包：</p>

<pre><code class="language-bash">$ electron-packager . electron-demo
</code></pre>

<h2 id="总结">总结</h2>

<p>本文首先对 electron 做了简单的介绍，然后讲解了 electron 进程的概念，其进程包括主进程和渲染进程。然后创建了一个简单的 electron 应用，并通过实现一个简单的应用场景，对 electron 进程间的通信做了实践。总体来说，使用 electron 创建桌面客户端的开发体验跟写 Node.js 和网页差不多。但本文对内置模块比如 app、ipcMain、ipcRender、remote 等的介绍比较粗浅，涉及到一些内置模块的使用，还需要继续查询 electron 的<a href="https://github.com/electron/electron/blob/master/docs/README.md">官方文档</a>，只有实践越多，才能越熟悉。</p>

<blockquote>
<p><a href="https://github.com/nodejh/nodejh.github.io/issues/39">https://github.com/nodejh/nodejh.github.io/issues/39</a></p>
</blockquote>

<p>&ndash;</p>

<ul>
<li><a href="https://github.com/nodejh/electron-quick-start">https://github.com/nodejh/electron-quick-start</a></li>
<li><a href="https://github.com/electron/electron">electron/electron</a></li>
<li><a href="https://weishuai.gitbooks.io/electron-/content/">electron中文教程</a></li>
<li><a href="https://wizardforcel.gitbooks.io/electron-doc/content/">Electron 中文文档</a></li>
<li><a href="https://github.com/pfan123/electron-docs">Electron开发桌面应用</a></li>
</ul>

                </section>
            </article>

            

            
                <div id="disqus_thread"></div>
<script type="text/javascript">
    var disqus_shortname = 'nodejh'; 

     
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</div>

            

            
                <ul id="post-list" class="archive readmore">
    <h3>Read more</h3>
    
    
        
        <li>
            <a href="http://nodejh.com/post/how-to-vertically-middle-align-floated-elements-with-flexbox/">使用 Flexbox 使浮动元素垂直居中<aside class="dates">Jul 28</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/css-responsive-navigation-menu/">纯 CSS 实现响应式导航菜单<aside class="dates">Jul 28</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/vue2-tutorials-02-todolist/">实现一个TodoList - Vue2 Tutorials (二)<aside class="dates">Jul 17</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/electron-quick-start/">Electron 快速入门<aside class="dates">Jul 6</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/vue2-tutorials-01-quick-start/">快速入门 - Vue2 Tutorials (一)<aside class="dates">Jul 6</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/project-documentation-with-hexo-static-site-generator/">使用 Hexo 创建项目文档网站<aside class="dates">Jul 5</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/handy-mysql-commands/">常用 MySQL 命令<aside class="dates">Mar 30</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/understand-the-oracle-startup-process/">深入理解 Oracle 启动原理<aside class="dates">Mar 26</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/setting-environmental-variables-in-macos/">macOS/Linux 环境变量设置<aside class="dates">Mar 19</aside></a>
        </li>
        
   
    
        
        <li>
            <a href="http://nodejh.com/post/how-to-create-auto-increment-column-in-oracle/">在 Oracle 中设置自增列<aside class="dates">Feb 20</aside></a>
        </li>
        
   
</ul>
            

            <footer id="footer">
    
        <div id="social">

	
	
    <a class="symbol" href="https://www.facebook.com/nodejh">
        <i class="fa fa-facebook-square"></i>
    </a>
    
    <a class="symbol" href="https://www.github.com/nodejh">
        <i class="fa fa-github-square"></i>
    </a>
    
    <a class="symbol" href="https://www.twitter.com/nodejh">
        <i class="fa fa-twitter-square"></i>
    </a>
    


</div>

    
    <p class="small">
    
       © Copyright 2017 <i class="fa fa-heart" aria-hidden="true"></i> nodejh
    
    </p>
    <p class="small">
        Powered by <a href="http://www.gohugo.io/">Hugo</a> Theme By <a href="https://github.com/nodejh/hugo-theme-cactus-plus">nodejh</a>
    </p>
</footer>

        </section>

        <script src="http://nodejh.com/js/jquery-2.2.4.min.js"></script>
<script src="http://nodejh.com/js/main.js"></script>
<script src="http://nodejh.com/js/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>




  
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-84989670-1', 'auto');
ga('send', 'pageview');
</script>




<script>
var baiduAnalytics = '39843ea392201290bd6f76173d2e1633';
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?" + baiduAnalytics;
  var s = document.getElementsByTagName("script")[0];
  s.parentNode.insertBefore(hm, s);
})();
</script>


    </body>
</html>
